~4Dgifts/toolbox/src/exampleCode/smoke README Texture Mapped `smoke' special effects implementation to see the demo (with decent results), run the "smoke.sh" script. smokeNoTex is an executable built showing plain polygons (see Makefile). the rest of this file is the text portions of the Smoke.sc showcase file: ------------------------------------------------------------------------- This program started out as a enhancement-request from General Dynamics to Deneb Robotics, for Deneb's factory-floor simulation products. There were two parts to the request: 1. a visualization of the source smoke or plume. 2. visualizing a compartment filling with smoke according to some rudimentary CFD calculations. I only focused on step 1, with a pointer to the `pfuSmoke' functionality inside our Performer product. Unfortunately, there were just too many technical hurdles to allow us (myself and the lead gfx programmer at Deneb) to utilize the Performer functionality directly. So, I took matters into my own hands and peeked in the source... The Performer utility code I looked at had: 1) an algorithm for translating single quad polygons along a direction vector over time while scaling the quad, and decreasing opacity (increasing transparency?)... void drawSmoke(uSmoke *smoke, uVec3 eye) { double now = uGetTime(); double deltaTime, age = 1.0f; ... /* Compute time elapsed from previous uDrawSmoke */ if(smoke->prevTime < 0.0f) deltaTime = 0.0f; else deltaTime = now - smoke->prevTime; if(smoke->duration > 0.0f) { age = 1.0 - (now - smoke->startTime) / smoke->duration; ... } ... puffInterval = smoke->puffInterval / age; puffDiss = smoke->dissipation/smoke->density * age; puff->t = (now - puff->startTime) / puffDiss; ... puff->transp = puff->t * puff->t * puff->t * puff->t; /* Radius is linear interpolation between radius and expansion */ puff->radius = (1.0f - puff->transp) * smoke->radius + puff->transp * smoke->expansion * smoke->radius ... /* Translate puff based on velocity vector */ tmp = puff->origin; uAddVec3(tmp, tmp, dist); ... if(smoke->tex) { uApplyTex(smoke->tex); } drawPuff(puff, smoke, eye); } void uApplyTex(uTexture *tex) { if(tex == NULL) { fprintf(stderr,"uApplyTex() Null uTexture.\n"); return; } texbind(TX_TEXTURE_0, tex->index); } 2) code to cause all the quads to be rotated to face the eye-point in world coordinate space... void drawPuff(uPuff *puff, uSmoke *smoke, uVec3 eye) { ... /* * Compute matrix which rotates puff to face the eyepoint */ uSubVec3(toEye, eye, puff->origin); uMakeRotOntoMat(mat, vec, toEye); for(i=0; i<4; i++) { uVec3 svert; /* Scale quad to current radius */ uScaleVec3(svert, puff->radius, quad[i]); /* Offset puff based on radius */ svert[1] -= smoke->radius; /* Rotate puff to follow viewer */ uXformPt3(dquad[i], svert, mat); /* Translate puff to origin */ uAddVec3(dquad[i], dquad[i], puff->origin); } ... } ***I know this part can and should be done by our hardware; I just haven't had time to finish the change. 3) a texture map with alpha-channel that would be modulated with the alpha value of the quad. void drawPuff(uPuff *puff, uSmoke *smoke, uVec3 eye) { uVec4 opaque; long i; ... #define Zv 0.0 static float t2[]={1,0,Zv,1,1,Zv,0,1,Zv,0,0,Zv}; ... uAddScaledVec3(opaque, smoke->bgnColor, puff->t, smoke->deltaColor); opaque[3] = 1.0f - puff->transp; ... #if NOTEXTURE #define T2F(t) #else #define T2F(t) t2f((t)) #endif /*Draw a puff...finally*/ lmcolor(LMC_DIFFUSE); c4f(opaque); bgnpolygon(); cross(dquad[0],dquad[1],dquad[2],&norm[0],&norm[1],&norm[2]); norm_d[0]=(dquad[0][0]-norm[0]); norm_d[1]=(dquad[0][1]-norm[1]); norm_d[2]=(dquad[0][2]-norm[2]); n3f(norm); T2F(t2); v3f(dquad[0]); T2F(t2+3); v3f(dquad[1]); T2F(t2+6); v3f(dquad[2]); T2F(t2+9); v3f(dquad[3]); endpolygon(); #if NOTEXTURE bgnline(); v3f(dquad[0]); v3f(norm_d); endline(); #endif } There were two stumbling blocks for me, using the smoke utility: 1) finding out how to properly setup the texture mapping properties and the texture environment. So, with a little help from some 4Dgifts code that worked... /* * Parameters for texturing. */ float texps[] = { TX_MAGFILTER, TX_BILINEAR, TX_MINFILTER, TX_MIPMAP_BILINEAR, TX_WRAP_S, TX_CLAMP, TX_WRAP_T, TX_CLAMP, TX_NULL }; float tevps[] = {TV_MODULATE,TV_NULL}; 2) Determining what values to use for afunction(), blendfunc(), and zwritemask(): Thanks to the Performer source tree, i was able to construct the following: void uDrawSmokes(uVec3 eye) { long i, n; zwritemask(0x00000000); if (UGET_GFX_TYPE() & PFGFX_HPC_40NS) /*express gfx*/ afunction(0,AF_NOTEQUAL); else afunction(40,AF_GREATER); blendfunction(BF_SA, BF_MSA); n = smokeCount; for(i=0; i<n; i++) { if(smokeList[i]->mode != USMOKE_STOP) { drawSmoke(smokeList[i], eye); } } blendfunction(BF_ONE, BF_ZERO); zwritemask(0xffffffff); } Creating the textures for stand-alone 'smoke' was alot easier than trying to do it through the full-blown performer interface: in uTex.c... long uLoadTexFile(uTexture *tex, char *name) { IMAGE *image_in; char path[_MAXSTRING]; long ret; if(tex == NULL) { fprintf(stderr,"uLoadTexFile() Null uTexture.\n"); return -1; } if(!uFindFile(name, path, R_OK)) { fprintf(stderr,"uLoadTexFile: could not find image file %s\n",name); return FALSE; } if ((image_in = iopen(path, "r")) == NULL) { fprintf(stderr,"uLoadTexFile: could not load image %s\n", name); return FALSE; } ret = uBuildTex(tex, name, image_in); iclose(image_in); return ret; } in main.c... void add_fire_tex(void) { fireTex = uNewTex(); uLoadTexFile(fireTex, "fire.texture.rgb"); texdef2d(fireTex->index,fireTex->comp,fireTex->sx,fireTex->sy, (unsigned long *)fireTex->image,0,texps); } void inittex(void) { smokeTex = uNewTex(); uLoadTexFile(smokeTex, "smoke.texture.rgb"); texdef2d(smokeTex->index,smokeTex->comp,smokeTex->sx,smokeTex->sy, (unsigned long *)smokeTex->image,0,texps); add_fire_tex(); tevdef(1,2,tevps); tevbind(0,1); }
Source
Documentation
Images
Reference